﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ChartEdit
{
    public class NoteTracks : List<NoteTrack>
    {
        public bool ContainsTrack( string name )
        {
            foreach ( NoteTrack track in this )
            {
                if ( track.Name == name )
                    return true;
            }
            return false;
        }

        public NoteTrack this[ string name ]
        {
            get
            {
                foreach ( NoteTrack track in this )
                {
                    if ( track.Name == name )
                        return track;
                }
                return null;
            }
        }
    }

    public class NoteTrack : List<Note>
    {
        // name of the track "ExpertSingle", etc
        public string HopoLogic;
        public string Name { get; set; }

        public string Difficulty { get; set; }
        public string Instrument { get; set; }

        public int HighestFret=0;

        public Note this[int index,int fret]
        {
            get
            {
                if (fret > HighestFret-1)
                    return null;
                if (noteList[fret].Count() <= index)
                    return null;
                return noteList[fret][index];
            }
        }
        public List<Note> NoteList(int fret)
        {
            if (fret >= noteList.Count())
                return null;
            return noteList[fret];
        }
        public void FillNoteList(int fret)
        {
            while(HighestFret<=fret)
            {
                HighestFret++;
                noteList.Add(new List<Note>());
                noteList.Last().Clear();
            }
        }

        public List<List<Note>> noteList = new List<List<Note>>();
        public List<Note> SpecialList = new List<Note>();
        public List<Note> EventList = new List<Note>();

        /*public void GenerateFretList()
        {
            noteList.Clear();
            HighestFret = highestFret()+1;
            for (int i = 0; i < HighestFret; i++)
            {
                noteList.Add(new List<Note>());
                foreach (Note n in this)
                {
                    if(n.Fret==i)
                        noteList[i].Add(n);
                }
            }
        }*/

        public void GenerateFretList()
        {
            noteList.Clear();
            SpecialList.Clear();
            EventList.Clear();
            HighestFret = highestFret() + 1;
            for (int i = 0; i < HighestFret; i++)
            {
                noteList.Add(new List<Note>());
            }
            foreach (Note n in this)
            {
                switch(n.Type)
                {
                    case(NoteType.Regular):
                        noteList[n.Fret].Add(n);
                        break;
                    case (NoteType.Special):
                        SpecialList.Add(n);
                        break;
                    case (NoteType.Event):
                        EventList.Add(n);
                        break;
                }
            }
            
        }

        public new int Count(int fret)
        {
            if (fret > noteList.Count-1) return 0;
            return noteList[fret].Count();
        }

        private int highestFret()
        {
            int hF = 0;
            foreach (Note n in this)
            {
                if (n.Fret > hF)
                    hF = n.Fret;
            }
            return hF;
        }

        public NoteTrack()
        {
        }
        public void SetHopoLogic()
        {
            HopoLogic = Globals.Instance.GameProperties[0].TrackProperties[stripDifficulty(Name)].HopoLogic;
        }
        private string stripDifficulty(string entry)
        {
            int index = 0;
            for (int x = 1; x < entry.Length; x++)
            {
                if (char.IsUpper(entry[x]))
                {
                    index = x;
                    break;
                }
            }
                return entry.Substring(index);
        }
        public NoteTrack( string name )
        {
            this.Name = name;

            SetDetails( name );
        }

        void SetDetails( string entry )
        {
            int index = 0;

            for ( int x = 1 ; x < entry.Length ; x++ )
            {
                if ( char.IsUpper( entry[ x ] ) )
                {
                    index = x;
                    break;
                }
            }

            Difficulty = entry.Substring( 0, index );
            Instrument = entry.Substring( index, entry.Length - index );
        }

        #region Property Assigning
        public void SetProperties( bool isANote, bool flipsHOPO, bool forcesHOPO,
    bool forcesStrum, int pointValue )
        {
            foreach ( Note n in this )
            {
                n.IsANote = isANote;
                n.FlipsHOPO = flipsHOPO;
                n.ForcesHOPO = forcesHOPO;
                n.ForcesStrum = forcesStrum;
                n.PointValue = pointValue;
            }
        }


        public void AssignProperty( int fret, bool isANote, bool FlipsHOPO )
        {
            foreach ( Note n in this )
            {
                if ( n.Fret == fret )
                {
                    n.IsANote = isANote;
                    n.FlipsHOPO = FlipsHOPO;
                }
            }
        }
        public void AssignProperty(int fret, bool isANote, bool flipsHOPO, bool forcesHOPO,
    bool forcesStrum, int pointValue)
        {
            foreach (Note n in this)
            {
                if (n.Fret == fret)
                {
                    n.IsANote = isANote;
                    n.FlipsHOPO = flipsHOPO;
                    n.ForcesHOPO = forcesHOPO;
                    n.ForcesStrum = forcesStrum;
                    n.PointValue = pointValue;
                }
            }
        }
        #endregion

        #region Evaluations
        public void Evaluate(Chart chart)
        {

            SetHopoLogic();
            EvaluateNoteness();
            EvaluateChords();
            EvaluateHOPO(chart.Resolution / 3, 1, this.Count());
            EvaluateFlipping();
            EvaluateSP();
        }
        public void Evaluate(Chart chart, int start, int start2, int end)
        {
            for (; end < this.Count() - 1; end++)
            {
                if (!this[end].IsChord)
                    break;
            }
            SetHopoLogic();
            EvaluateNoteness(start,end);
            EvaluateChords(start2,end);
            EvaluateHOPO(chart.Resolution / 3, start2, end);
            EvaluateFlipping(start, end);
            EvaluateSP(start, end);
        }
        public void EvaluateNoteness() { EvaluateNoteness(0, this.Count()); }

        public void EvaluateNoteness(int start, int end)
        {
            for (int i = start; i < end; i++)
            {
                if (this[i].Type != NoteType.Regular)
                    this[i].IsANote = false;
            }
        }

        public void EvaluateChords() { EvaluateChords(1, this.Count()); }
        public void EvaluateChords(int start, int end)
        {
            //Make sure isANote is set before performing this
            for (int i = start; i < end; i++)
            {
                if (this[i].IsANote)
                {
                    for (int j = i - 1; j > 0; j--)
                    {
                        if (this[j].IsANote)
                        {
                            if (this[j].Offset == this[i].Offset)
                            {
                                this[i].IsChord = true;
                                this[j].IsChord = true;
                            }
                            else
                            {
                                this[i].IsChord = false;
                            }
                            break;
                        }
                    }
                }
                else
                {
                    this[i].IsChord = false;
                }
            }
        }

        public void EvaluateHOPO()
        {
            EvaluateHOPO(768 / 12, 1, this.Count());
        }

        public void EvaluateHOPO(int threshold, int start, int end)
        {
            for (int i = start/*Start at 1 because the first is never hopo*/; i < end; i++)
            {
                //If it's a chord, it's obviously not hopo
                if (this[i].IsChord)
                    this[i].IsHopo = false;
                else
                {
                    //If the distance between the two is great than the hopo threshold then it's not hopo
                    int j = i - 1;
                    while (!this[j].IsANote | this[j].Offset == this[i].Offset)
                    {
                        if (j == 0)
                            break;
                        j--;
                    }
                    if (this[i].Offset - this[j].Offset > threshold)
                        this[i].IsHopo = false;
                    else
                    {
                        //Hopo logic between games switches here. Assume GH3 for now
                        switch (this.HopoLogic)
                        {
                            default: //Default to GH3 logic
                                {
                                    if (this[i].Fret != this[j].Fret | this[j].IsChord)
                                        this[i].IsHopo = true;
                                    else
                                        this[i].IsHopo = false;
                                    break;
                                }
                            //NEVERSOFT
                            case ("gh3"):
                            case ("gh4"):
                            case ("gh5"):
                            case ("ghm"):
                            case ("gha"):
                            case ("ghwt"):
                            case ("ghvh"):
                            case ("bh"):
                                {
                                    if (this[i].Fret != this[j].Fret | this[j].IsChord)
                                        this[i].IsHopo = true;
                                    else
                                        this[i].IsHopo = false;
                                    break;
                                }
                            //HARMONIX
                            case ("gh1"):
                            case ("gh2"):
                            case ("rockband"):
                            case ("rockband2"):
                                {
                                    for (int k = j; k > 0; k--)
                                    {
                                        if (this[j].Offset == this[k].Offset)
                                        {
                                            if (this[i].Fret != this[k].Fret)
                                                this[i].IsHopo = true;
                                            else
                                            {
                                                this[i].IsHopo = false;
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            this[i].IsHopo = true;
                                            break;
                                        }
                                    }


                                    break;
                                }
                            case ("none"):
                                {
                                    //THIS SEEMS GROSSLY INEFFICIENT BUT FUCK IT
                                    this[i].IsHopo = false;
                                    break;
                                }
                        }
                    }
                }
            }
        }

        public void EvaluateFlipping() { EvaluateFlipping(0, this.Count()); }
        public void EvaluateFlipping(int start, int end)
        {
            for (int i = start; i < end; i++)
            {
                if (this[i].FlipsHOPO)
                {
                    for (int j = i - 1; j > 0; j--)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = !this[j].IsHopo;
                        else break;
                    }
                    for (int j = i + 1; j < this.Count(); j++)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = !this[j].IsHopo;
                        else break;
                    }
                }
                if (this[i].ForcesHOPO)
                {
                    for (int j = i - 1; j > 0; j--)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = true;
                        else break;
                    }
                    for (int j = i + 1; j < this.Count(); j++)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = true;
                        else break;
                    }
                }
                if (this[i].ForcesStrum)
                {
                    for (int j = i - 1; j > 0; j--)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = false;
                        else break;
                    }
                    for (int j = i + 1; j < this.Count(); j++)
                    {
                        if (this[j].Offset == this[i].Offset)
                            this[j].IsHopo = false;
                        else break;
                    }
                }
            }
        }

        public void EvaluateSP(int start, int end)
        {
            int spIndex=-1;
            for (int j = spIndex + 1; j < SpecialList.Count(); j++)
            {
                if (SpecialList[j].SpecialFlag == 2 && SpecialList[j].OffsetEnd > this[start].Offset)
                {
                    spIndex = j;
                    break;
                }
            }
            if (spIndex == -1)
            {
                for (int i = start; i < end; i++)
                    this[i].IsInSP = false;
                return;
            }

            for (int i = start; i < end; i++)
            {
                if (this[i].Offset >= SpecialList[spIndex].Offset)
                {
                    if (this[i].Offset < SpecialList[spIndex].OffsetEnd)
                    {
                        this[i].IsInSP = true;
                    }
                    else
                    {
                        this[i].IsInSP = false;
                        for (int j = spIndex + 1; j < SpecialList.Count(); j++)
                        {
                            if (SpecialList[j].SpecialFlag == 2 && SpecialList[j].OffsetEnd > this[i].Offset)
                            {
                                spIndex = j;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    this[i].IsInSP = false;
                }

            }

        }
        public void EvaluateSP()
        {
            int spOffsetEnd = 0;
            List<int> indexes = new List<int>();
            int lastNewOffset = 0;
            for (int i = 0; i < this.Count(); i++)
            {
                if (this[i].Offset != lastNewOffset)
                {
                    indexes.Clear();
                    lastNewOffset = this[i].Offset;
                }
                indexes.Add(i);
                if (this[i].Type == NoteType.Special & this[i].SpecialFlag == 2)
                {
                    spOffsetEnd = this[i].OffsetEnd;
                    foreach (int n in indexes)
                    {
                        this[n].IsInSP = true;
                    }
                    indexes.Clear();
                }
                else
                {
                    if (this[i].Offset < spOffsetEnd)
                        this[i].IsInSP = true;
                    else
                        this[i].IsInSP = false;
                }

            }
        } 
        #endregion
    }

    public enum NoteType
    {
        Regular,
        Special,
        Event,
    }
    public class Note
    {
        static readonly Regex NoteRegex = new Regex( "(?<offset>\\d+)\\s*\\=\\s*(?<type>.*?)\\s*(?<fret>\\d+)\\s*(?<length>\\d+)", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline );
        static readonly Regex NoteEventRegex = new Regex( @"(?<offset>\d+)\s*\=\s*E\s*(?<name>.*)", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline );

        public int Offset { get; set; }

        public NoteType Type { get; set; }

        public int SpecialFlag { get; set; }

        public string EventName { get; set; }

        public int Fret { get; set; }
        public int Length { get; set; }

        public Note()
        {
        }
        public Note(NoteProp noteProp)
        {
            Type = NoteType.Regular;
            IsANote = noteProp.IsNote;
            FlipsHOPO = noteProp.FlipsHOPO;
            ForcesHOPO = noteProp.ForcesHOPO;
            ForcesStrum = noteProp.ForcesStrum;
            PointValue = noteProp.PointValue;
            Fret = noteProp.Fret;
        }


        public int OffsetEnd
        {
            get { return Offset + Length; }
            set { Length = value - Offset; }
        }

        // EDITOR PROPERTIES //
        //variable
        public bool IsHopo { get; set; }
        public bool IsChord { get; set; }
        public bool IsInSP { get; set; }
        public bool IsInSlide { get; set; }
        public bool IsArmored { get; set; }
        //constant
        public bool IsANote { get; set; }
        public bool FlipsHOPO { get; set; }
        public bool ForcesHOPO { get; set; }
        public bool ForcesStrum { get; set; }
        public bool ArmoredNote { get; set; }
        public int PointValue { get; set; }
        // ***           *** //


        public static Note Parse( string entry )
        {
            Match match = NoteRegex.Match( entry );

            Note note = new Note();

            if ( !match.Success )
            {
                match = NoteEventRegex.Match( entry );

                if ( !match.Success )
                    return null;

                int offset = int.Parse( match.Groups[ "offset" ].Value.Trim() );
                string name = match.Groups[ "name" ].Value.Trim();

                note.Offset = offset;
                note.Type = NoteType.Event;
                note.EventName = name;

                return note;
            }


            int off = int.Parse( match.Groups[ "offset" ].Value.Trim() );
            string type = match.Groups[ "type" ].Value.Trim();
            int fret = int.Parse( match.Groups[ "fret" ].Value.Trim() );
            int length = int.Parse( match.Groups[ "length" ].Value.Trim() );

            note.Offset = off;

            switch ( type )
            {
                case "N":
                    note.Type = NoteType.Regular;
                    note.Fret = fret;
                    break;

                case "S":
                    note.Type = NoteType.Special;
                    note.SpecialFlag = fret;
                    break;
            }

            note.Length = length;

            return note;
        }
    }
}
